<template>
  <div class="container">
  </div>
</template>

<script type="text/javascript-6">
import * as d3 from 'd3'
import ajax from '../../../../api/ajax'
import api from '../../../../api/url'

export default {
  name: 'CloudChart',

  props: {
    source: {
      type: Array,
      required: true,
      default () {
        return []
      }
    }
  },

  data () {
    return {
      svg: null,
      width: 1000,
      height: 500,
      color: null,
      simulation: null,
      link: null,
      node: null,
      debounceTimer: null,
      nodes: [],
      links: []
    }
  },

  mounted () {
    this.init()
  },

  methods: {
    init () {
      this.svg = d3.select('.container')
        .append('svg')
        .attr('oncontextmenu', 'return false;')
        .attr('width', this.width)
        .attr('height', this.height)

      this.color = d3.scaleOrdinal(d3.schemeCategory20)

      this.simulation = d3.forceSimulation()
        .force('link', d3.forceLink().id(d => d.id).distance(180))
        .force('charge', d3.forceManyBody().strength(-10))
        .force('center', d3.forceCenter(this.width / 2, this.height / 2))

      this.deep(this.source, (data) => {
        this.updateNodes(data)
        this.updateLinks(data)
      })

      this.link = this.svg.append('g')
        .selectAll('line')
        .data(this.links)
        .enter()
        .append('line')
        .attr('stroke', '#aaa')
        .attr('stroke-opacity', 0.3)

      const nodeG = this.svg.append('g')
        .selectAll('g')
        .data(this.nodes)
        .enter()
        .append('g')

      this.node = nodeG
        .append('circle')
        .attr('r', 20)
        .attr('fill', d => this.color(d.id.substr(0, d.id.length - 2)))
        // .attr('fill', d => this.color(d.id))
        .attr('fill-opacity', 0.3)
        .style('cursor', 'pointer')
        .on('mouseenter', this.highlightNodes)
        .on('mouseleave', () => {
          this.node.attr('fill-opacity', 0.3)
          this.link.attr('stroke', '#aaa').attr('stroke-opacity', 0.3)
        })
        .on('click', (node, i, doms) => {
          // 获取到 当前点击的 标签， 接下来 要跳转到 树状图页面
          this.$emit('nodeSelect', node.id)
        })
        .call(d3.drag()
          .on('start', () => {})
          .on('drag', () => {})
          .on('end', () => {}))

      this.node.append('title')
        .text(item => item.label)

      setTimeout(() => {
        nodeG.append('text')
        .attr('x', d => d.x)
        .attr('y', d => d.y)
        .attr('text-anchor', 'middle')
        .attr('font-size', 14)
        .text(d => {
          return d.shortLabel ? d.shortLabel : d.label
        })
      }, 3000)

      this.simulation.nodes(this.nodes)
        .on('tick', this.ticked)

      this.simulation.force('link')
        .links(this.links)

      // this.updateSearchBox()
    },

    ticked () {
      this.link
        .attr('x1', d => d.source.x)
        .attr('y1', d => d.source.y)
        .attr('x2', d => d.target.x)
        .attr('y2', d => d.target.y)

      this.node
        .attr('cx', d => d.x)
        .attr('cy', d => d.y)
    },

    updateNodes (data) {
      const item = { id: data.id, label: data.label, shortLabel: data.shortLabel, weight: data.weight, children: [] }
      this.nodes = [...this.nodes, item]
    },

    updateLinks (data) {
      const item = { source: data.id, sourceName: data.label }
      data.children.forEach(subItem => {
        const newItem = { ...item, target: subItem.id, targetName: subItem.label }
        this.links = [...this.links, newItem]
      })
    },

    highlightNodes (node) {
      // this.node
      //   .attr('fill-opacity', 0.3)
      //   .filter(item => item.id.startsWith(node.id))
      //   .transition()
      //   .duration(500)
      //   .attr('fill-opacity', 1)

      // this.link
      //   .attr('stroke', '#aaa')
      //   .attr('stroke-opacity', 0.3)
      //   .attr('stroke', '#000')
      //   .attr('stroke-opacity', 1)
      ajax.post(api.USER_TAG_LIST, {'cust_no': node.id})
        .then(res => {
          const resId = res.data.map(item => item['to_cont_id'])
          this.node.attr('fill-opacity', 0.3)
            .filter(item => resId.includes(item.id) || item.id === node.id)
            .transition()
            .duration(500)
            .attr('fill-opacity', 1)
        }).catch(err => {
          console.error(err)
        })
    },

    updateSearchBox () {
      let html = `
        <input id="search" type="text"><br>
        <ul id="tags"></ul>
      `

      d3.select('.container').insert('div', 'svg')
        .attr('class', 'search-box')
        .style('margin-bottom', '30px')
        .style('width', '200px')
        .style('text-align', 'left')
        .html(html)
        .on('input', this.debounce(this.search))

      d3.select('#tags')
        .style('margin-top', '10px')
        .selectAll('li')
        .data(this.source)
        .enter()
        .append('li')
        .html(item => `<span class="tag-icon" style="background-color: ${this.color(item.id.substr(0, item.id.length - 2))}"></span><span data-id="${item.id}">${item.label}</span>`)
        .selectAll('.tag-icon')
        .style('margin-right', '6px')
        .style('display', 'inline-block')
        .style('vertical-align', 'middle')
        .style('width', '16px')
        .style('height', '16px')
        .style('border-radius', '4px')

      d3.select('#tags')
        .selectAll('li span:last-of-type')
        .style('cursor', 'pointer')
        .on('click', (...args) => {
          const nodes = args[2]
          const nodeIndex = args[1]
          const nodeId = nodes[nodeIndex].dataset.id
          const node = this.$parent.findNode(nodeId, this.source)
          this.highlightNodes(node)
        })
    },

    search () {
      d3.select('#search').property('value')
    },

    debounce (fn, delay = 500) {
      if (typeof fn !== 'function') {
        console.error('fn is not a function.')
        return null
      }

      return () => {
        if (this.debounceTimer) {
          return null
        } else {
          this.debounceTimer = setTimeout(() => {
            fn()
            clearTimeout(this.debounceTimer)
            this.debounceTimer = null
          }, delay)
        }
      }
    },

    deep (data, callback) {
      if (!Array.isArray(data)) {
        console.error('data in method deep is not an Array type.')
        return null
      }

      for (let i = 0; i < data.length; i++) {
        const item = data[i]
        const children = item.children
        if (children && Array.isArray(children) && children.length > 0) {
          callback(item)
          this.deep(children, callback)
        } else {
          callback(item)
        }
      }
    }
  }
}
</script>

<style lang="stylus" scoped>
.container
  padding 10px 20px
  min-height 100%
  background-color #fff
</style>
